jsPlumb

jsPlumb provides a means for a developer to visually connect elements on their web pages. It uses SVG in modern browsers, and VML on IE 8 and below.

jsPlumb can be used with jQuery, MooTools or YUI3 (or another library of your choice if you feel like implementing an adapter for it).

The latest version is 1.5.2.

Required Imports and Basic Setup

I strongly encourage you to read this entire page. There are a few things you need to know about integrating jsPlumb with your UI.

  1. Browser Compatibility
  2. Setup
  3. Doctype
  4. Required Imports
  5. Initializing jsPlumb
  6. Multiple jsPlumb Instances
  7. Z-Index Considerations
  8. Where does jsPlumb add elements?
  9. Dragging
  10. Performance
  11. Skeleton Page

Browser Compatibility

jsPlumb runs on everything from IE6 up. There are some caveats, though, because of various browser/library bugs:

Setup

Doctype

text/html

If you're serving your pages with content type text/html, as most people are, then you should use this doctype:

<!doctype html>

This gives you Standards mode in all browsers and access to HTML5.

application/xhtml+xml

If you're serving application/xhtml+xml then you need to include the VML namespace in the html element:

<?xml version="1.0" encoding="UTF-8"?>
<html xmlns='http://www.w3.org/1999/xhtml' xmlns:v="urn:schemas-microsoft-com:vml">

Don't forget that in IE<9, XHTML is not supported.

Required Imports

jQuery
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.9.2/jquery-ui.min.js"></script>
<script type="text/javascript" src="PATH_TO/jquery.jsPlumb-1.5.0-min.js "></script>
Touch Events

jQuery <= 1.9.x does not support touch events, but there is a neat plugin you can use for this - jQuery UI Touch Punch. You need to include it in your page after both jQuery and jQueryUI.

MooTools
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/mootools/1.3.3/mootools-yui-compressed.js"></script>
<script type="text/javascript" src="PATH_TO_MOO_TOOLS_MORE_1_3_2_1"></script>
<script type="text/javascript" src="PATH_TO/mootools.jsPlumb-1.5.0-min.js "></script>
Touch Events

There is currently no solution to enable touch events with MooTools and jsPlumb. At least, none that I know of right now. Maybe The Google can help you with this.

YUI3
<script type="text/javascript" src="http://yui.yahooapis.com/3.3.0/build/simpleyui/simpleyui-min.js"></script>
<script type="text/javascript" src="PATH_TO/yui.jsPlumb-1.5.0-min.js "></script>
Touch Events

YUI supports touch events out of the box.

Initializing jsPlumb

You should not start making calls to jsPlumb until the DOM has been initialized - perhaps no surprises there. To handle this, you should bind to the ready event on jsPlumb (or the instance of jsPlumb you are working with):

jsPlumb.bind("ready", function() {
...            
// your jsPlumb related init code goes here
...
});

There's a helper method that can save you a few precious characters:

jsPlumb.ready(function() {
...            
// your jsPlumb related init code goes here
...
});

If you bind to the ready event after jsPlumb has already been initialized, your callback will be executed immediately.

Multiple jsPlumb instances

jsPlumb is registered on the browser's Window by default, providing one static instance for the whole page to use. Should you need to, though, you can instantiate independent instances of jsPlumb, using the getInstance method, for example:

var firstInstance = jsPlumb.getInstance();

The variable firstInstance can now be treated exactly as you would treat the jsPlumb variable - you can set defaults, call the connect method, whatever:

firstInstance.importDefaults({
  Connector : [ "Bezier", { curviness: 150 } ],
  Anchors : [ "TopCenter", "BottomCenter" ]
});

firstInstance.connect({
  source:"element1", 
  target:"element2", 
  scope:"someScope" 
});

getInstance optionally takes an object that provides the defaults:

var secondInstance = jsPlumb.getInstance({
  PaintStyle:{ 
    lineWidth:6, 
    strokeStyle:"#567567", 
    outlineColor:"black", 
    outlineWidth:1 
  },
  Connector:[ "Bezier", { curviness: 30 } ],
  Endpoint:[ "Dot", { radius:5 } ],
  EndpointStyle : { fillStyle: "#567567"  },
  Anchor : [ 0.5, 0.5, 1, 1 ]
});

secondInstance.connect({ source:"element4", target:"element3", scope:"someScope" });

Z-index Considerations

You need to pay attention to the z-indices of the various parts of your UI when using jsPlumb, in particular to ensure that the elements that jsPlumb adds to the DOM do not overlay other parts of the interface.

jsPlumb adds an element to the DOM for each Endpoint, Connector and Overlay. So for a connection having visible Endpoints at each end and a label in the middle, jsPlumb adds four elements to the DOM. The actual elements it adds depend on the renderer in use (Canvas/SVG/VML).

To help you organise z-indices correctly, jsPlumb adds a CSS class to each type of element it adds. They are as follows:

ComponentClass
Endpoint_jsPlumb_endpoint
Connector_jsPlumb_connector
Overlay_jsPlumb_overlay

In addition, whenever the mouse is hovering over an Endpoint or Connection, that component is assigned the class _jsPlumb_hover. For more information about styling jsPlumb with CSS, see this page

Where does jsPlumb add elements?

It's important to understand where in the DOM jsPlumb will add any elements it creates, as it has a bearing on the markup you can use with jsPlumb.

Early versions of jsPlumb added everything to the end of the body element. This has the advantage of being the most flexible arrangement in terms of supporting which elements can be connected, but in certain use cases produced unexpected results. Consider the arrangement where you have some connected elements in a tab: you would expect jsPlumb to add elements inside the tab, so that when the user switches tabs and the current one is hidden, all the jsPlumb stuff is hidden too. But when the elements are on the body, this does not happen!

For this reason, jsPlumb's default behaviour is now to attach Endpoint elements to the parent of the element the Endpoint is attached to (not the actual element the Endpoint is attached to), and to attach Connector elements to the parent of the source Endpoint in the Connection. This results in all the elements of the Connection being siblings inside the same parent node, which jsPlumb requires, for various reasons arising from the trade off between doing lots of maths and the performance of the whole thing.

In general, the default behaviour means that you cannot do things like this:

<body>
    <div id="container0">
        <div id="node0></div>
    </div>
    <div id="container1">
        <div id="node1"></div>
    </div>
</body>

<script type="text/javascript">    
    var e0 = jsPlumb.addEndpoint("node0"),
    e1 = jsPlumb.addEndpoint("node1");

    jsPlumb.connect({ source:e0, target:e1 });
</script>

...because the elements created for e0 and e1 would have different parent nodes, and an attempt to make a Connection between them would not work. For the example just given, you would need to register the Endpoints on the container divs (and then quite possibly throw away the node divs):

<body>
    <div id="container0">
    </div>
    <div id="container1">
    </div>
<body>

<script type="text/javascript">
  var e0 = jsPlumb.addEndpoint("container0"),
      e1 = jsPlumb.addEndpoint("container1");
    jsPlumb.connect({ source:e0, target:e1 });
</script>

now 'e0' and 'e1' have the same parent - the body element.

Container

You can instruct jsPlumb to use some element as the parent of everything jsPlumb adds to the UI through usage of the jsPlumb.Defaults.Container property, or you can set a container parameter on calls to addEndpoint or connect (if this is set and a default is also set, this default value is actually used. This may seem counter-intuitive, and could of course be changed if there is feedback from the community that this would be a good idea).

It is recommended that you do this.

Some examples:

Element Dragging

A common feature of interfaces using jsPlumb is that the elements are draggable. Unless you absolutely need to configure this behaviour using your library's underlying method, you should use jsPlumb.draggable:

jsPlumb.draggable("elementId");

You can also pass a selector from your library in to this method:

jsPlumb.draggable($(".someClass"));

This method is just a wrapper for the underlying library's draggable functionality, and there is a two-argument version in which the second argument is passed down to the underlying library (example below).

Drag Containment

A common request is for the ability to contain the area within which an element may be dragged. For jQuery this is as simple as providing a containment parameter:

jsPlumb.draggable($("someSelector"), {
  containment:"parent"
});
YUI

Drag containment is also supported if you are using YUI - for this you can supply either a constrain2Node parameter (this is YUI's parameter name), or containment, as with jQuery, and jsPlumb will convert it:

jsPlumb.draggable($("someSelector"), {
  constrain2Node:Y.one("aNode")
});

Note that YUI does not support the string "parent" as a value, and jsPlumb does not do anything to massage the argument you pass in. So you cannot do this:

jsPlumb.draggable($("someSelector"), {
  constrain2Node:"parent"
});  
MooTools

Drag containment is not supported in MooTools.

Dragging nested elements

jsPlumb takes nesting into account when handling draggable elements. For example, say you have this markup:

<div id="container">
  <div class="childType1"></div>
  <div class="childType2"></div>
</div>

...and then you connect one of those child divs to something, and make container draggable:

jsPlumb.connect({
    source:$("#container .childType1"),
    target:"somewhere else"
});

jsPlumb.draggable("container");

Now when you drag container, jsPlumb will have noticed that there are nested elements that have Connections, and they will be updated. Note that the order of operations is not important here: if container was already draggable when you connected one of its children to something, you would get the same result.

Nested Element offsets

For performance reasons, jsPlumb caches the offset of each nested element relative to its draggable ancestor. If you make changes to the draggable ancestor that will have resulted in the offset of one or more nested elements changing, you need to tell jsPlumb about it, using the recalculateOffsets function.

Consider the example from before, but with a change to the markup after initializing everything:

<div id="container">
  <div class="header" style="height:20px;background-color:blue;">header</div>
  <div class="childType1"></div>
  <div class="childType2"></div>
<div>

...and then you connect one of those child divs to something, and make "container" draggable:

jsPlumb.connect({
  source:$("#container .childType1"),
  target:"somewhere else"
});

jsPlumb.draggable("container");

...

$("#container .header).hide();    // hide the header bar. this will alter the offset of the other child elements...
jsPlumb.recalculateOffsets("container");   // tell jsPlumb that the internal dimensions have changed.
// you can also use a selector, eg $("#container")

Selection while dragging

The default browser behaviour on mouse drag is to select elements in the DOM. jQuery suppresses this behaviour, but MooTools and YUI do not. To assist with handling this, the each library adapter attaches this class to the body at drag start:

_jsPlumb_drag_select

The class is removed at drag end.

A suitable value for this class (this is from the jsPlumb demo pages) is:

._jsPlumb_drag_select * {
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;    
}    

Performance

The speed at which jsPlumb executes, and the practical limit to the number of manageable connections, is greatly affected by the browser upon which it is being run. At the time of writing, it will probably not surprise you to learn that jsPlumb runs fastest in Chrome, followed by Safari/Firefox, and then IE browsers in descending version number order.

Suspending Drawing

Every connect or addEndpoint call in jsPlumb ordinarily causes a repaint of the associated element, which for many cases is what you want. But if you are performing some kind of "bulk" operation - like loading data on page load perhaps - it is recommended that you suspend drawing before doing so:

jsPlumb.setSuspendDrawing(true);
...
- load up all your data here -
...
jsPlumb.setSuspendDrawing(false, true);

Notice the second argument in the last call to setSuspendDrawing: it instructs jsPlumb to immediately perform a full repaint (by calling, internally, repaintEverything).

I said above it is recommended that you do this. It really is. It makes an enormous difference to the load time when you're dealing with a lot of Connections on slower browsers.

doWhileSuspended

This function abstracts out the pattern of suspending drawing, doing something, and then re-enabling drawing:

jsPlumb.doWhileSuspended(fn, [doNotRepaintAfterwards]);

Example usage:

jsPlumb.doWhileSuspended(function() {
  // import a billion things here
});

By default, this will run a repaint at the end. But you can suppress that, should you want to:

jsPlumb.doWhileSuspended(function() {
  // import a billion things here
}, true);

Note just in case you take it literally, you should probably not attempt to import a billion things into jsPlumb. You may find that performance suffers a little.

Anchor Types

Continuous anchors are the type that require the most maths, because they have to calculate their position every time a paint cycle occurs.

Dynamic and Perimeter anchors are the next slowest (with Perimeter being slower than most Dynamic Anchors as they are actually Dynamic Anchors that have, by default, 60 locations to choose from).

Static anchors like Top, Bottom etc are the fastest.